﻿using System;
using System.Reflection.Emit;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace Aeon.Emulator.Decoding.Emitters
{
    /// <summary>
    /// Emitter for decoding an operand that may be a register or a memory address using 16-bit addressing.
    /// </summary>
    internal sealed class LoadRegRmw16 : LoadRegRmw
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the LoadRegRmw16 class.
        /// </summary>
        /// <param name="state">Common emitter state information.</param>
        /// <param name="valueSize">Size of the value to load in bytes.</param>
        /// <param name="memoryOnly">Indicates whether the operand will only be a memory address.</param>
        /// <param name="floatingPoint">Indicates whether the value is a floating-point value in memory.</param>
        /// <param name="offsetOnly">Indicates whether only an effective address should be generated.</param>
        public LoadRegRmw16(EmitStateInfo state, int valueSize, bool memoryOnly, bool floatingPoint, bool offsetOnly)
            : base(state, valueSize, memoryOnly, floatingPoint, offsetOnly)
        {
        }
        /// <summary>
        /// Initializes static fields of the LoadRegRmw16 class.
        /// </summary>
        static LoadRegRmw16()
        {
            OffsetCalculators = new IntPtr[8];
            for(int i = 0; i < OffsetCalculators.Length; i++)
            {
                var method = typeof(LoadRegRmw16).GetMethod(string.Format("RM{0}", i), BindingFlags.NonPublic | BindingFlags.Static);
                OffsetCalculators[i] = method.MethodHandle.GetFunctionPointer();
            }
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets the type of local variable needed to store the operand value as a method argument.
        /// </summary>
        public override Type MethodArgType
        {
            get
            {
                if(this.OffsetOnly)
                    return typeof(ushort);
                else
                    return base.MethodArgType;
            }
        }
        #endregion

        #region Protected Methods
        /// <summary>
        /// Loads the linear 32-bit address of a memory operand.
        /// </summary>
        /// <param name="rmLocal">Local variable containing the R/M code.</param>
        /// <param name="modLocal">Local variable containing the Mod code.</param>
        protected override void LoadPhysicalAddress(LocalBuilder rmLocal, LocalBuilder modLocal)
        {
            GenerateAddress(rmLocal, modLocal, false);
        }
        /// <summary>
        /// Loads the 16-bit offset of a memory operand.
        /// </summary>
        /// <param name="rmLocal">Local variable containing the R/M code.</param>
        /// <param name="modLocal">Local variable containing the Mod code.</param>
        protected override void LoadAddressOffset(LocalBuilder rmLocal, LocalBuilder modLocal)
        {
            GenerateAddress(rmLocal, modLocal, true);
        }
        #endregion

        #region Private Methods
        private void GenerateAddress(LocalBuilder rmLocal, LocalBuilder modLocal, bool offsetOnly)
        {
            Label modNotZeroLabel = il.DefineLabel();
            Label rmSixLabel = il.DefineLabel();
            Label gotDisplacement = il.DefineLabel();
            Label modTwoLabel = il.DefineLabel();
            Label doneLabel = il.DefineLabel();

            // Calculate and load the displacement.
            il.LoadLocal(modLocal);
            il.Emit(OpCodes.Brtrue_S, modNotZeroLabel);
            il.LoadLocal(rmLocal);
            il.LoadConstant(6);
            il.Emit(OpCodes.Ceq);
            il.Emit(OpCodes.Brtrue_S, rmSixLabel);

            // Load a displacement of 0.
            IncrementIPPointer(1);
            il.LoadConstant(0);
            il.Emit(OpCodes.Br, gotDisplacement);

            // If rm == 6, load *(short*)(ip + 1).
            il.MarkLabel(rmSixLabel);
            //LoadProcessor();
            LoadIPPointer();
            il.LoadConstant(1);
            il.Emit(OpCodes.Add);
            il.Emit(OpCodes.Ldind_U2);

            if(!offsetOnly)
            {
                LoadBaseAddress16(rmLocal, () =>
                {
                    LoadProcessor();
                    il.Emit(OpCodes.Ldfld, Infos.Processor.SegmentBases);
                    il.LoadConstant((int)SegmentIndex.DS);
                    il.Emit(OpCodes.Conv_I);
                    il.Emit(OpCodes.Sizeof, typeof(uint));
                    il.Emit(OpCodes.Mul);
                    il.Emit(OpCodes.Add);
                });
                il.Emit(OpCodes.Add);
                il.Emit(OpCodes.Conv_U4);
            }

            //il.Emit(OpCodes.Call, Infos.RuntimeCalls.GetMoffsAddress16);
            IncrementIPPointer(3);
            il.Emit(OpCodes.Br_S, doneLabel);

            // If mod != 0, check for 1, else 2.
            il.MarkLabel(modNotZeroLabel);
            il.LoadLocal(modLocal);
            il.LoadConstant(1);
            il.Emit(OpCodes.Ceq);
            il.Emit(OpCodes.Brfalse_S, modTwoLabel);

            // If mod == 1, load *(byte*)(ip + 1).
            LoadIPPointer();
            il.LoadConstant(1);
            il.Emit(OpCodes.Add);
            il.Emit(OpCodes.Ldind_I1);
            IncrementIPPointer(2);
            il.Emit(OpCodes.Br_S, gotDisplacement);

            // If mod == 2, load *(short*)(ip + 1).
            il.MarkLabel(modTwoLabel);
            LoadIPPointer();
            il.LoadConstant(1);
            il.Emit(OpCodes.Add);
            il.Emit(OpCodes.Ldind_I2);
            IncrementIPPointer(3);

            // Finshed getting displacement, call GetAddress.
            il.MarkLabel(gotDisplacement);
            var displacementLocal = il.DeclareLocal(typeof(ushort));
            il.StoreLocal(displacementLocal);

            GetModRMAddress16(rmLocal, displacementLocal);

            if(!offsetOnly)
            {
                LoadBaseAddress16(rmLocal, () =>
                {
                    LoadProcessor();
                    il.Emit(OpCodes.Ldfld, Infos.Processor.DefaultSegments16);
                    il.LoadLocal(rmLocal);
                    il.Emit(OpCodes.Conv_I);
                    il.Emit(OpCodes.Sizeof, typeof(uint).MakePointerType());
                    il.Emit(OpCodes.Mul);
                    il.Emit(OpCodes.Add);
                    il.Emit(OpCodes.Ldind_I);
                });

                il.Emit(OpCodes.Add);
                il.Emit(OpCodes.Conv_U4);
            }

            // Mark the end of the function.
            il.MarkLabel(doneLabel);
            if(offsetOnly)
                il.Emit(OpCodes.Conv_U2);
        }

        /// <summary>
        /// Generates code to put the final linear address from a ModRM memory operand onto the stack.
        /// </summary>
        /// <param name="rmLocal">Local variable containing the RM value.</param>
        /// <param name="displacementLocal">Local variable containing the displacement.</param>
        private void GetModRMAddress16(LocalBuilder rmLocal, LocalBuilder displacementLocal)
        {
            // getModRMOffset16[rm](processor, displacement)
            //il.Emit(OpCodes.Ldsfld, Infos.RuntimeCalls.GetModRMOffset16);
            //il.LoadLocal(rmLocal);
            //il.Emit(OpCodes.Ldelem_Ref);

            LoadProcessor();
            il.LoadLocal(displacementLocal);
            //il.Emit(OpCodes.Callvirt, typeof(Func<Processor, ushort, ushort>).GetMethod("Invoke"));

            il.Emit(OpCodes.Ldsfld, typeof(LoadRegRmw16).GetField("OffsetCalculators", BindingFlags.Static | BindingFlags.Public));
            il.LoadLocal(rmLocal);
            il.Emit(OpCodes.Ldelem_I);

            var sigHelper = SignatureHelper.GetMethodSigHelper(CallingConventions.Standard, typeof(ushort));
            sigHelper.AddArguments(new[] { typeof(Processor), typeof(ushort) }, null, null);

            il.Emit(OpCodes.Calli, sigHelper);
        }
        /// <summary>
        /// Loads the appropriate segment base address onto the stack.
        /// </summary>
        /// <param name="rmLocal">Local variable containing the RM value.</param>
        /// <param name="loadDefaultBase">Method to generate the default segment base address.</param>
        private void LoadBaseAddress16(LocalBuilder rmLocal, Action loadDefaultBase)
        {
            LoadProcessor();
            il.Emit(OpCodes.Ldfld, Infos.Processor.BaseOverrides);
            LoadProcessor();
            il.Emit(OpCodes.Ldfld, Infos.Processor.SegmentOverride);
            il.Emit(OpCodes.Conv_I);
            il.Emit(OpCodes.Sizeof, typeof(uint).MakePointerType());
            il.Emit(OpCodes.Mul);
            il.Emit(OpCodes.Add);
            il.Emit(OpCodes.Ldind_I);
            il.Emit(OpCodes.Dup);

            var gotBase = il.DefineLabel();
            il.Emit(OpCodes.Brtrue_S, gotBase);

            il.Emit(OpCodes.Pop);

            loadDefaultBase();

            il.MarkLabel(gotBase);
            il.Emit(OpCodes.Ldind_U4);
        }
        #endregion

        private static ushort RM0(Processor p, ushort d)
        {
            return (ushort)(p.BX + p.SI + (int)d);
        }
        private static ushort RM1(Processor p, ushort d)
        {
            return (ushort)(p.BX + p.DI + (int)d);
        }
        private static ushort RM2(Processor p, ushort d)
        {
            return (ushort)(p.BP + p.SI + (int)d);
        }
        private static ushort RM3(Processor p, ushort d)
        {
            return (ushort)(p.BP + p.DI + (int)d);
        }
        private static ushort RM4(Processor p, ushort d)
        {
            return (ushort)(p.SI + (int)d);
        }
        private static ushort RM5(Processor p, ushort d)
        {
            return (ushort)(p.DI + (int)d);
        }
        private static ushort RM6(Processor p, ushort d)
        {
            return (ushort)(p.BP + (int)d);
        }
        private static ushort RM7(Processor p, ushort d)
        {
            return (ushort)(p.BX + (int)d);
        }

        public static readonly IntPtr[] OffsetCalculators;
    }
}
